#include "map.hh"

#include "../content-handler.hh"

void print (const Pos& p)
{
  std::cout << p.x << " | " << p.y << " | " << p.z << std::endl;
}


Map::Map ():
  v_ (0),
  size_ (-1),
  built_ (false),
  vbo_ (-1),
  ibo_ (-1),
  disp_ (0)
{
}

Map::Map (int size):
  v_ (0),
  size_ (size),
  built_ (false),
  vbo_ (0),
  ibo_ (0),
  disp_ (0),
  tex_ (ContentHandler::get ()->texture ("map"))
{
  v_ = new List<int>**[size];
  for (int i = 0; i < size; ++i)
    {
      v_[i] = new List<int>*[size];
      for (int j = 0; j < size; ++j)
	v_[i][j] = 0;
    }
}

Map::~Map ()
{
  List<int>* tmp = 0;
  List<int>* pred = 0;

  for (int i = 0; i < size_; ++i)
    {
      for (int j = 0; j < size_; ++j)
  	{
  	  tmp = v_[i][j];
  	  pred = tmp;
  	  while (pred != 0)
  	    {
  	      tmp = tmp->next;
  	      delete pred;
  	      pred = tmp;
  	    }
  	}
      delete[] v_[i];
    }
  delete[] v_;
}

void
Map::init_bos ()
{
  init_vbo ();
  init_ibo ();

  //sending vbo and ibo to the cg
  //vbo
  glBufferDataARB (GL_ARRAY_BUFFER_ARB, size_ * size_ * sizeof (Point), disp_,
  		   GL_DYNAMIC_DRAW_ARB);
  //ibo
  glBufferDataARB (GL_ELEMENT_ARRAY_BUFFER_ARB, (size_ - 1) * (size_ - 1) * 6 * sizeof (uint),
  		   index_, GL_STATIC_DRAW);
}

void normalize (Normal* n)
{
  float l = sqrt (n->x * n->x + n->y * n->y + n->z * n->z);

  n->x /= l;
  n->y /= l;
  n->z /= l;
}

Normal vectorial (const Pos& p1, const Pos& p2, const Pos& p3)
{
  Normal res;

  float v1x = p2.x - p1.x;
  float v1y = p2.y - p1.y;
  float v1z = p2.z - p1.z;

  float v2x = p3.x - p1.x;
  float v2y = p3.y - p1.y;
  float v2z = p3.z - p1.z;

  res.x = v1y * v2z - v1z * v2y;
  res.y = v1z * v2x - v1x * v2z;
  res.z = v1x * v2y - v1y * v2x;

  normalize (&res);

  return res;
}

void addnnormalize (Normal* out, const Normal& in)
{
  out->x += in.x;
  out->y += in.y;
  out->z += in.z;

  normalize (out);
}

void
Map::init_ibo ()
{
  //creating IBO
  glGenBuffersARB (1, &ibo_);
  glBindBufferARB (GL_ELEMENT_ARRAY_BUFFER_ARB, ibo_);

  Point* p1;
  Point* p2;
  Point* p3;
  Normal n;

  //squared map : we have at least (n-1)*(n-1)*2* 3 <= 3 indices
  index_ = new uint[(size_ - 1) * (size_ - 1) * 6];
  for (int i = 0; i < size_ - 1; ++i)
  {
    for (int j = 0; j < size_ - 1; ++j)
    {
      index_[(i * (size_ - 1) + j) * 6 + 0] = i * size_ + j + 1;
      index_[(i * (size_ - 1) + j) * 6 + 1] = (i + 1) * size_ + j;
      index_[(i * (size_ - 1) + j) * 6 + 2] = i * size_ + j;

      //compute the normal
      p1 = (Point*) &disp_[i * size_ + j + 1];
      p2 = (Point*) &disp_[(i + 1) * size_ + j];
      p3 = (Point*) &disp_[i * size_ + j];

      n = vectorial (p1->p, p2->p, p3->p);
      addnnormalize (&p1->n, n);
      addnnormalize (&p2->n, n);
      addnnormalize (&p3->n, n);

      index_[(i * (size_ - 1) + j) * 6 + 3] = i * size_ + j + 1;
      index_[(i * (size_ - 1) + j) * 6 + 4] = (i + 1) * size_ + j + 1;
      index_[(i * (size_ - 1) + j) * 6 + 5] = (i + 1) * size_ + j;

      //compute the normal
      p1 = (Point*) &disp_[i * size_ + j + 1];
      p2 = (Point*) &disp_[(i + 1) * size_ + j + 1];
      p3 = (Point*) &disp_[(i + 1) * size_ + j];

      n = vectorial (p1->p, p2->p, p3->p);
      addnnormalize (&p1->n, n);
      addnnormalize (&p2->n, n);
      addnnormalize (&p3->n, n);
    }
  }
}

void
Map::init_vbo ()
{
 //creating the VBO
  glGenBuffersARB (1, &vbo_);

  glBindBufferARB (GL_ARRAY_BUFFER_ARB, vbo_);

  disp_ = new Point[size_ * size_];

  //filling it
  for (int i = 0; i < size_; ++i)
  {
    for (int j = 0; j < size_; ++j)
    {
      disp_[i * size_ + j % size_].p.x = v_[i][j]->elt;
      disp_[i * size_ + j % size_].p.y = j;
      disp_[i * size_ + j % size_].p.z = i;

      //set the normal value to 0 atm, these values will be computed while
      //creating triangles
      disp_[i * size_ + j % size_].n.x = 0;
      disp_[i * size_ + j % size_].n.y = 0;
      disp_[i * size_ + j % size_].n.z = 0;

      //fill the texture coordinates
      disp_[i * size_ + j % size_].t.u = (float) i / size_;
      disp_[i * size_ + j % size_].t.v = (float) j / size_;
    }
  }
}

// void
// Map::dispatch () const
// {
//   List<int>* li = 0;

//   glBegin (GL_POINTS);

//   for (int i = 0; i < size_; ++i)
//     for (int j = 0; j < size_; ++j)
//       {
// 	li = v_[i][j];
// 	if (li->next)
// 	  glColor3d (0, 255, 0);
// 	else
// 	  glColor3d (0, 0, 255);

// 	// if (i < 128 && j < 128)
// 	//   glColor3d (255, 0, 0);

// 	while (li != 0)
// 	  {
// 	    glVertex3d (li->elt, j, i);
// 	    li = li->next;
// 	  }
//       }
//   glEnd ();
// }

// void
// Map::dispatch () const
// {
//   glEnableClientState (GL_VERTEX_ARRAY);
//   glColor3ub (255, 0, 0);
//   glBindBufferARB (GL_ARRAY_BUFFER_ARB, vbo_);
  
//   glVertexPointer (3, GL_INT, 0, 0);
//   glDrawArrays (GL_POINTS, 0, size_ * size_);

//   glDisableClientState (GL_VERTEX_ARRAY);
// }

void
Map::dispatch () const
{
  glBindBufferARB (GL_ARRAY_BUFFER_ARB, vbo_);

  glEnableClientState (GL_VERTEX_ARRAY);
  glVertexPointer (3, GL_INT, sizeof (Point), 0);
 
  glEnableClientState (GL_NORMAL_ARRAY);
  glNormalPointer (GL_FLOAT, sizeof (Point), (void*) sizeof (Pos));

  glBindTexture (GL_TEXTURE_2D, tex_->id ());
  glColor3ub (255, 255, 255);

  glClientActiveTexture (GL_TEXTURE0);
  glEnableClientState (GL_TEXTURE_COORD_ARRAY);
  glTexCoordPointer (2, GL_FLOAT, sizeof (Point), (void*) (sizeof (Pos) + sizeof (Normal)));

  glBindBufferARB (GL_ELEMENT_ARRAY_BUFFER_ARB, ibo_);
  glDrawElements (GL_TRIANGLES, 6 * (size_ - 1) * (size_ - 1), GL_UNSIGNED_INT, 0);

  glDisableClientState (GL_VERTEX_ARRAY);
  glDisableClientState (GL_NORMAL_ARRAY);
  glBindTexture (GL_TEXTURE_2D, 0);
}

void
Map::operator() (Vector3d* p)
{
  // get current point node_list
  List<int>* li = v_[(*p) (0)][(*p) (1)];

  if (li == 0) // create the point
    {
      li = new List<int>;
      li->elt = (*p) (2);
      li->next = 0;
      v_[(*p) (0)][(*p) (1)] = li;
    }
  else
    {
      List<int>* tmp = 0;
      List<int>* pred = li;

      // insert the point in the sorted list
      while ((li != 0) && ((*p) (2) < li->elt))
      {
	pred = li;
	li = li->next;
      }

      tmp = new List<int>;
      tmp->next = li;
      tmp->elt = (*p) (2);

      if (pred != li)
	pred->next = tmp;
      else
	v_[(*p) (0)][(*p) (1)] = tmp;
    }
}
